先來完成上一章節最後提到的 Remove List
作業。
syntax = "proto3";
option csharp_namespace = "Todo.Grpc";
service TodoListGrpcService {
rpc CreateTodoList (CreateTodoListRequest) returns (TodoListResponse);
rpc RemoveTodoList (RemoveTodoListRequest) returns (TodoListResponse);
}
message CreateTodoListRequest {
string UserId = 1;
string Name = 2;
string Description = 3;
}
message RemoveTodoListRequest {
string Id = 1;
}
message TodoListResponse {
string Id = 1;
string UserId = 2;
string Name = 3;
string Description = 4;
string Status = 5;
}
using Grpc.Core;
using Todo.Application;
namespace Todo.Grpc.Services;
public class GrpcTodoListService : TodoListGrpcService.TodoListGrpcServiceBase
{
private readonly ITodoListService _todoListService;
public GrpcTodoListService(ITodoListService todoListService)
{
this._todoListService = todoListService;
}
public override Task<TodoListResponse> CreateTodoList(CreateTodoListRequest request, ServerCallContext context)
{
var result = _todoListService.CreateTodoList(new Guid(request.UserId), request.Name, request.Description);
TodoListResponse response = new()
{
Id = result.Id.ToString(),
UserId = result.UserId.ToString(),
Name = result.Name,
Description = result.Description,
Status = result.Status.ToString()
};
return Task.FromResult(response);
}
public override Task<TodoListResponse> RemoveTodoList(RemoveTodoListRequest request, ServerCallContext context)
{
var result = _todoListService.RemoveTodoList(new Guid(request.Id));
TodoListResponse response = new()
{
Id = result.Id.ToString(),
UserId = result.UserId.ToString(),
Name = result.Name,
Description = result.Description,
Status = result.Status.ToString()
};
return Task.FromResult(response);
}
}
namespace Todo.Application;
public interface ITodoListService
{
TodoListResult CreateTodoList(Guid userId, string name, string description);
TodoListResult RemoveTodoList(Guid guid);
}
using System.ComponentModel;
using Todo.Domain.Aggregates;
namespace Todo.Application;
public class TodoListService : ITodoListService
{
private static readonly List<TodoList> _todoLists = new();
public TodoListResult CreateTodoList(Guid userId, string name, string description)
{
var list = TodoList.Create(name, description, userId);
_todoLists.Add(list);
return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
}
public TodoListResult RemoveTodoList(Guid guid)
{
var list = _todoLists.SingleOrDefault(i => i.Id.Value == guid);
if (list == null)
throw new ArgumentException("Todo list not exists");
list.Remove();
return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
}
}
我們在 Todo Service 的實作上,在 Application Layer 的 Services 都直接使用一個 Static List 來存在記憶體,現在需要把功能分離出 Application Layer 並交由 Infrastructure 來實作,這時候就要建立一個 Repository 來使業思務邏輯與資料庫的 CRUD 解耦。Repository Pattern 的核心想是讓程式碼只與 Repository 介面(IRepository)進行互動,而不直接操作資料庫或 ORM(如 Entity Framework)。透過具體實作這個介面的類別來與資料庫進行實際的操作。這樣,當你需要切換資料庫或更改資料存取邏輯時,只需要修改 Repository 的實作,而不會影響到其他程式碼。
先用 ITodoListRepository
和 ITodoItemRepository
介面取代 Application Service 上所有相關的操作
using Todo.Domain.Aggregates;
namespace Todo.Application;
public class TodoListService : ITodoListService
{
private readonly ITodoListRepository _todoListRepository;
public TodoListService(ITodoListRepository todoListRepository)
{
this._todoListRepository = todoListRepository;
}
public TodoListResult CreateTodoList(Guid userId, string name, string description)
{
var list = TodoList.Create(name, description, userId);
_todoListRepository.Add(list);
return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
}
public TodoListResult RemoveTodoList(Guid guid)
{
var list = _todoListRepository.GetByGuid(guid);
if (list == null)
throw new ArgumentException("Todo list not exists");
list.Remove();
return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
}
}
using Todo.Domain.Aggregates;
using Todo.Domain.ValueObjects;
namespace Todo.Application;
public class TodoItemService : ITodoItemService
{
private readonly ITodoItemRepository _todoItemRepository;
public TodoItemService(ITodoItemRepository todoItemRepository)
{
this._todoItemRepository = todoItemRepository;
}
public TodoItemResult CreateTodoItem(Guid userId, string content)
{
var item = TodoItem.Create(content, userId);
_todoItemRepository.Add(item);
return new TodoItemResult(item.Id.Value, item.ListId, item.Content, item.Status);
}
public TodoItemResult FinishTodoItem(Guid guid)
{
var item = _todoItemRepository.GetByGuid(guid);
if (item == null)
throw new ArgumentException("Todo item not exists");
if (item.Status != new TodoItemStatus(Domain.ValueObjects.Enums.TodoItemState.Todo))
throw new ArgumentException("Todo item cannot be finished");
item.MarkAsFinished();
return new TodoItemResult(item.Id.Value, item.ListId, item.Content, item.Status);
}
public TodoItemResult RemoveTodoItem(Guid guid)
{
var item = _todoItemRepository.GetByGuid(guid);
if (item == null)
throw new ArgumentException("Todo item not exists");
item.Remove();
return new TodoItemResult(item.Id.Value, item.ListId, item.Content, item.Status);
}
}
接著產生 ITodoListRepository
和 ITodoItemRepository
介面
using Todo.Domain.Aggregates;
namespace Todo.Application;
public interface ITodoListRepository : IRepository<TodoList>
{
void Add(TodoList list);
TodoList? GetByGuid(Guid guid);
}
using Todo.Domain.Aggregates;
namespace Todo.Application;
public interface ITodoItemRepository : IRepository<TodoItem>
{
void Add(TodoItem item);
TodoItem? GetByGuid(Guid guid);
}
然後在 Infrastructure Layer 建立 TodoListRepository
和 TodoItemRepository
實作
using Common.Library.Seedwork;
using Todo.Application;
using Todo.Domain.Aggregates;
namespace Todo.Infrastructure;
public class TodoListRepository : ITodoListRepository
{
private static readonly List<TodoList> todoLists = new List<TodoList>();
public IUnitOfWork UnitOfWork => throw new NotImplementedException();
public void Add(TodoList list)
{
todoLists.Add(list);
}
public TodoList? GetByGuid(Guid guid)
{
return todoLists.SingleOrDefault(x => x.Id.Value == guid);
}
}
using Common.Library.Seedwork;
using Todo.Application;
using Todo.Domain.Aggregates;
namespace Todo.Infrastructure;
public class TodoItemRepository : ITodoItemRepository
{
private static readonly List<TodoItem> TodoItems = new List<TodoItem>();
public IUnitOfWork UnitOfWork => throw new NotImplementedException();
public void Add(TodoItem item)
{
TodoItems.Add(item);
}
public TodoItem? GetByGuid(Guid guid)
{
return TodoItems.SingleOrDefault(x => x.Id.Value == guid);
}
}
接著 DI 剛做好的 Repositories。新增 TodoInfrastructureRegister.cs
如下:
using Microsoft.Extensions.DependencyInjection;
using Todo.Application;
namespace Todo.Infrastructure;
public static class TodoInfrastructureRegister
{
public static IServiceCollection AddTodoInfrastructure(this IServiceCollection services)
{
services.AddScoped<ITodoListRepository, TodoListRepository>();
services.AddScoped<ITodoItemRepository, TodoItemRepository>();
return services;
}
}
然後在 Program.cs
使用 AddTodoInfrastructure()
builder.Services.AddTodoApplication().AddTodoInfrastructure();
簡單測試一下所有功能都還正常
下一章節實作 Todo Service 的 ORM,讓資料真的存進 Database 內。